////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package mx.validators
{

    //import mx.events.FlexEvent;
    //import org.apache.royale.events.IEventDispatcher;
    //import mx.events.ValidationResultEvent;
	//[ResourceBundle("validators")]
    
/**
 *  The EmailValidator class validates that a String has a single &#64; sign,
 *  a period in the domain name and that the top-level domain suffix has
 *  two, three, four, or six characters.
 *  IP domain names are valid if they are enclosed in square brackets. 
 *  The validator does not check whether the domain and user name
 *  actually exist.
 *
 *  <p>You can use IP domain names if they are enclosed in square brackets; 
 *  for example, myname&#64;[206.132.22.1].
 *  You can use individual IP numbers from 0 to 255.</p>
 *  
 *  @mxml
 *
 *  <p>The <code>&lt;mx:EmailValidator&gt;</code> tag
 *  inherits all of the tag attributes of its superclass,
 *  and adds the following tag attributes:</p>
 *  
 *  <pre>
 *  &lt;mx:EmailValidator 
 *    invalidCharError="Your e-mail address contains invalid characters."
 *    invalidDomainError= "The domain in your e-mail address is incorrectly formatted." 
 *    invalidIPDomainError="The IP domain in your e-mail address is incorrectly formatted." 
 *    invalidPeriodsInDomainError="The domain in your e-mail address has consecutive periods." 
 *    missingAtSignError="An at sign (&64;) is missing in your e-mail address."
 *    missingPeriodInDomainError="The domain in your e-mail address is missing a period." 
 *    missingUsernameError="The username in your e-mail address is missing." 
 *    tooManyAtSignsError="Your e-mail address contains too many &64; characters."
 *  /&gt;
 *  </pre>
 *  
 *  @includeExample examples/EmailValidatorExample.mxml
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Royale 0.9.3
 */
public class EmailValidator extends Validator
{
	//include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static const DISALLOWED_LOCALNAME_CHARS:String =
								"()<>,;:\\\"[] `~!#$%^&*={}|/?\t\n\r";
	/**
	 *  @private
	 */							
	private static const DISALLOWED_DOMAIN_CHARS:String =
								"()<>,;:\\\"[] `~!#$%^&*+={}|/?'\t\n\r";
	
	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

    
    /**
     *  Convenience method for calling a validator
     *  from within a custom validation function.
     *  Each of the standard Flex validators has a similar convenience method.
     *
     *  @param validator The EmailValidator instance.
     *
     *  @param value A field to validate.
     *
     *  @param baseField Text representation of the subfield
     *  specified in the value parameter.
     *  For example, if the <code>value</code> parameter specifies value.email,
     *  the <code>baseField</code> value is "email".
     *
     *  @return An Array of ValidationResult objects, with one
     *  ValidationResult object for each field examined by the validator. 
     *
     *  @see mx.validators.ValidationResult	 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public static function validateEmail(validator:EmailValidator,
                                         value:Object,
                                         baseField:String):Array
    {
        var results:Array = [];
        
        // Validate the domain name
        // If IP domain, then must follow [x.x.x.x] format
        // Can not have continous periods.
        // Must have at least one period.
        // Must end in a top level domain name that has 2, 3, 4, or 6 characters.
        
        var emailStr:String = String(value);
        var username:String = "";
        var domain:String = "";
        var n:int;
        var i:int;
        
        // Find the @
        var ampPos:int = emailStr.indexOf("@");
        if (ampPos == -1)
        {
            results.push(new ValidationResult(
                true, baseField, "missingAtSign",
                validator.missingAtSignError));
            return results;
        }
            // Make sure there are no extra @s.
        else if (emailStr.indexOf("@", ampPos + 1) != -1) 
        { 
            results.push(new ValidationResult(
                true, baseField, "tooManyAtSigns",
                validator.tooManyAtSignsError));
            return results;
        }
        
        // Separate the address into username and domain.
        username = emailStr.substring(0, ampPos);
        domain = emailStr.substring(ampPos + 1);
        
        // Validate username has no illegal characters
        // and has at least one character.
        var usernameLen:int = username.length;
        if (usernameLen == 0)
        {
            results.push(new ValidationResult(
                true, baseField, "missingUsername",
                validator.missingUsernameError));
            return results;
        }
        
        for (i = 0; i < usernameLen; i++)
        {
            if (DISALLOWED_LOCALNAME_CHARS.indexOf(username.charAt(i)) != -1)
            {
                results.push(new ValidationResult(
                    true, baseField, "invalidChar",
                    validator.invalidCharError));
                return results;
            }
        }
        
        // name can't start with a dot
        if (username.charAt(0) == '.')
        {
            results.push(new ValidationResult(
                true, baseField, "invalidChar",
                validator.invalidCharError));
            return results;
        }
        
        var domainLen:int = domain.length;
        
        // check for IP address
        if ((domain.charAt(0) == "[") && (domain.charAt(domainLen - 1) == "]"))
        {
            // Validate IP address
            if (!isValidIPAddress(domain.substring(1, domainLen - 1)))
            {
                results.push(new ValidationResult(
                    true, baseField, "invalidIPDomain",
                    validator.invalidIPDomainError));
                return results;
            }
        }
        else
        {
            // Must have at least one period
            var periodPos:int = domain.indexOf(".");
            var nextPeriodPos:int = 0;
            var lastDomain:String = "";
            
            if (periodPos == -1)
            {
                results.push(new ValidationResult(
                    true, baseField, "missingPeriodInDomain",
                    validator.missingPeriodInDomainError));
                return results;
            }
            
            while (true)
            {
                nextPeriodPos = domain.indexOf(".", periodPos + 1);
                if (nextPeriodPos == -1)
                {
                    lastDomain = domain.substring(periodPos + 1);
                    break;
                }
                else if (nextPeriodPos == periodPos + 1)
                {
                    results.push(new ValidationResult(
                        true, baseField, "invalidPeriodsInDomain",
                        validator.invalidPeriodsInDomainError));
                    return results;
                }
                periodPos = nextPeriodPos;
            }
            
            // Check that there are no illegal characters in the domain.
            for (i = 0; i < domainLen; i++)
            {
                if (DISALLOWED_DOMAIN_CHARS.indexOf(domain.charAt(i)) != -1)
                {
                    results.push(new ValidationResult(
                        true, baseField, "invalidChar",
                        validator.invalidCharError));
                    return results;
                }
            }
            
            // Check that the character immediately after the @ is not a period or an hyphen.
            // And check that the character before the period is not an hyphen.
            if (domain.charAt(0) == "." || domain.charAt(0) == "-" || domain.charAt(periodPos - 1) == "-")
            {
                results.push(new ValidationResult(
                    true, baseField, "invalidDomain",
                    validator.invalidDomainError));
                return results;
            }
        }
        
        return results;
    }
    
    /**
     * Validate a given IP address
     * 
     * If IP domain, then must follow [x.x.x.x] format
     * or for IPv6, then follow [x:x:x:x:x:x:x:x] or [x::x:x:x] or some
     * IPv4 hybrid, like [::x.x.x.x] or [0:00::192.168.0.1]
     *
     * @private
     */ 
    private static function isValidIPAddress(ipAddr:String):Boolean
    {
        var ipArray:Array = [];
        var pos:int = 0;
        var newpos:int = 0;
        var item:Number;
        var n:int;
        var i:int;
        
        // if you have :, you're in IPv6 mode
        // if you have ., you're in IPv4 mode
        
        if (ipAddr.indexOf(":") != -1)
        {
            // IPv6
            
            // validate by splitting on the colons
            // to make it easier, since :: means zeros, 
            // lets rid ourselves of these wildcards in the beginning
            // and then validate normally
            
            // get rid of unlimited zeros notation so we can parse better
            var hasUnlimitedZeros:Boolean = ipAddr.indexOf("::") != -1;
            if (hasUnlimitedZeros)
            {
                ipAddr = ipAddr.replace(/^::/, "");
                ipAddr = ipAddr.replace(/::/g, ":");
            }
            
            while (true)
            {
                newpos = ipAddr.indexOf(":", pos);
                if (newpos != -1)
                {
                    ipArray.push(ipAddr.substring(pos,newpos));
                }
                else
                {
                    ipArray.push(ipAddr.substring(pos));
                    break;
                }
                pos = newpos + 1;
            }
            
            n = ipArray.length;
            
            const lastIsV4:Boolean = ipArray[n-1].indexOf(".") != -1;
            
            if (lastIsV4)
            {
                // if no wildcards, length must be 7
                // always, never more than 7
                if ((ipArray.length != 7 && !hasUnlimitedZeros) || (ipArray.length > 7))
                    return false;
                
                for (i = 0; i < n; i++)
                {
                    if (i == n-1)
                    {
                        // IPv4 part...
                        return isValidIPAddress(ipArray[i]);
                    }
                    
                    item = parseInt(ipArray[i], 16);
                    
                    if (item != 0)
                        return false;
                }
            }
            else
            {
                
                // if no wildcards, length must be 8
                // always, never more than 8
                if ((ipArray.length != 8 && !hasUnlimitedZeros) || (ipArray.length > 8))
                    return false;
                
                for (i = 0; i < n; i++)
                {
                    item = parseInt(ipArray[i], 16);
                    
                    if (isNaN(item) || item < 0 || item > 0xFFFF || ipArray[i] == "")
                        return false;
                }
            }
            
            return true;
        }
        
        if (ipAddr.indexOf(".") != -1)
        {
            // IPv4
            
            // validate by splling on the periods
            while (true)
            {
                newpos = ipAddr.indexOf(".", pos);
                if (newpos != -1)
                {
                    ipArray.push(ipAddr.substring(pos,newpos));
                }
                else
                {
                    ipArray.push(ipAddr.substring(pos));
                    break;
                }
                pos = newpos + 1;
            }
            
            if (ipArray.length != 4)
                return false;
            
            n = ipArray.length;
            for (i = 0; i < n; i++)
            {
                item = Number(ipArray[i]);
                if (isNaN(item) || item < 0 || item > 255 || ipArray[i] == "")
                    return false;
            }
            
            return true;
        }
        
        return false;
    }
	
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 *  
	 *  @langversion 3.0
	 *  @playerversion Flash 9
	 *  @playerversion AIR 1.1
	 *  @productversion Royale 0.9.3
	 */ 
	public function EmailValidator()
	{
		super();
	} 
	 
    //--------------------------------------------------------------------------
    //
    //  Properties: Errors
    //
    //--------------------------------------------------------------------------
    
    //----------------------------------
    //  invalidCharError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the invalidCharError property.
     */
    private var _invalidCharError:String;
    
    /**
     *  @private
     */
    private var invalidCharErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there are invalid characters in the e-mail address.
     *
     *  @default "Your e-mail address contains invalid characters."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get invalidCharError():String
    {
        return _invalidCharError;
    }
    
    /**
     *  @private
     */
    public function set invalidCharError(value:String):void
    {
        invalidCharErrorOverride = value;
        
        _invalidCharError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "invalidCharErrorEV"/*)*/;
    }
    
    //----------------------------------
    //  invalidDomainError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the invalidDomainError property.
     */
    private var _invalidDomainError:String;
    
    /**
     *  @private
     */
    private var invalidDomainErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when the suffix (the top level domain)
     *  is not 2, 3, 4 or 6 characters long.
     *
     *  @default "The domain in your e-mail address is incorrectly formatted."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get invalidDomainError():String
    {
        return _invalidDomainError;
    }
    
    /**
     *  @private
     */
    public function set invalidDomainError(value:String):void
    {
        invalidDomainErrorOverride = value;
        
        _invalidDomainError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "invalidDomainErrorEV"/*)*/;
    }
    
    //----------------------------------
    //  invalidIPDomainError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the invalidIPDomainError property.
     */
    private var _invalidIPDomainError:String;
    
    /**
     *  @private
     */
    private var invalidIPDomainErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when the IP domain is invalid. The IP domain must be enclosed by square brackets.
     *
     *  @default "The IP domain in your e-mail address is incorrectly formatted."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get invalidIPDomainError():String
    {
        return _invalidIPDomainError;
    }
    
    /**
     *  @private
     */
    public function set invalidIPDomainError(value:String):void
    {
        invalidIPDomainErrorOverride = value;
        
        _invalidIPDomainError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "invalidIPDomainError"/*)*/;
    }
    
    //----------------------------------
    //  invalidPeriodsInDomainError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the invalidPeriodsInDomainError property.
     */
    private var _invalidPeriodsInDomainError:String;
    
    /**
     *  @private
     */
    private var invalidPeriodsInDomainErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there are continuous periods in the domain.
     *
     *  @default "The domain in your e-mail address has continous periods."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get invalidPeriodsInDomainError():String
    {
        return _invalidPeriodsInDomainError;
    }
    
    /**
     *  @private
     */
    public function set invalidPeriodsInDomainError(value:String):void
    {
        invalidPeriodsInDomainErrorOverride = value;
        
        _invalidPeriodsInDomainError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "invalidPeriodsInDomainError"/*)*/;
    }
    
    //----------------------------------
    //  missingAtSignError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the missingAtSignError property.
     */
    private var _missingAtSignError:String;
    
    /**
     *  @private
     */
    private var missingAtSignErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there is no at sign in the email address.
     *
     *  @default "An at sign (&64;) is missing in your e-mail address."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get missingAtSignError():String
    {
        return _missingAtSignError;
    }
    
    /**
     *  @private
     */
    public function set missingAtSignError(value:String):void
    {
        missingAtSignErrorOverride = value;
        
        _missingAtSignError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "missingAtSignError"/*)*/;
    }
    
    //----------------------------------
    //  missingPeriodInDomainError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the missingPeriodInDomainError property.
     */
    private var _missingPeriodInDomainError:String;
    
    /**
     *  @private
     */
    private var missingPeriodInDomainErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there is no period in the domain.
     *
     *  @default "The domain in your e-mail address is missing a period."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get missingPeriodInDomainError():String
    {
        return _missingPeriodInDomainError;
    }
    
    /**
     *  @private
     */
    public function set missingPeriodInDomainError(value:String):void
    {
        missingPeriodInDomainErrorOverride = value;
        
        _missingPeriodInDomainError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "missingPeriodInDomainError"/*)*/;
    }
    
    //----------------------------------
    //  missingUsernameError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the missingUsernameError property.
     */
    private var _missingUsernameError:String;
    
    /**
     *  @private
     */
    private var missingUsernameErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there is no username.
     *
     *  @default "The username in your e-mail address is missing."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get missingUsernameError():String
    {
        return _missingUsernameError;
    }
    
    /**
     *  @private
     */
    public function set missingUsernameError(value:String):void
    {
        missingUsernameErrorOverride = value;
        
        _missingUsernameError = value != null ?
            value :
            /*resourceManager.getString(
                "validators", */"missingUsernameError"/*)*/;
    }
    
    //----------------------------------
    //  tooManyAtSignsError
    //----------------------------------
    
    /**
     *  @private
     *  Storage for the tooManyAtSignsError property.
     */
    private var _tooManyAtSignsError:String;
    
    /**
     *  @private
     */
    private var tooManyAtSignsErrorOverride:String;
    
    [Inspectable(category="Errors", defaultValue="null")]
    
    /**
     *  Error message when there is more than one at sign in the e-mail address.
     *  This property is optional. 
     *
     *  @default "Your e-mail address contains too many &64; characters."
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get tooManyAtSignsError():String
    {
        return _tooManyAtSignsError;
    }
    
    /**
     *  @private
     */
    public function set tooManyAtSignsError(value:String):void
    {
        tooManyAtSignsErrorOverride = value;
        
        _tooManyAtSignsError = value != null ?
            value :
            /*resourceManager.getString(
                "validators",*/ "tooManyAtSignsError"/*)*/;
    }
    
    //--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------
    
    /**
     *  @private    
    override protected function resourcesChanged():void
    {
        super.resourcesChanged();
        
        invalidCharError = invalidCharErrorOverride;
        invalidDomainError = invalidDomainErrorOverride;
        invalidIPDomainError = invalidIPDomainErrorOverride;
        invalidPeriodsInDomainError = invalidPeriodsInDomainErrorOverride;
        missingAtSignError = missingAtSignErrorOverride;
        missingPeriodInDomainError = missingPeriodInDomainErrorOverride;
        missingUsernameError = missingUsernameErrorOverride;
        tooManyAtSignsError = tooManyAtSignsErrorOverride;
    }
     */
    
    /**
     *  Override of the base class <code>doValidation()</code> method
     *  to validate an e-mail address.
     *
     *  <p>You do not call this method directly;
     *  Flex calls it as part of performing a validation.
     *  If you create a custom Validator class, you must implement this method. </p>
     *
     *  @param value Either a String or an Object to validate.
     *
     *  @return An Array of ValidationResult objects, with one ValidationResult 
     *  object for each field examined by the validator. 
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    override protected function doValidation(value:Object):Array
    {
        var results:Array = super.doValidation(value);
        
        // Return if there are errors
        // or if the required property is set to false and length is 0.
        var val:String = value ? String(value) : "";
        if (results.length > 0 || ((val.length == 0) && !required))
            return results;
        else
            return EmailValidator.validateEmail(this, value, null);
    }
}

}
